package ec.linde.modulos.dao;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import ec.linde.enumeraciones.Estado;
import ec.linde.enumeraciones.TipoNegocio;
import ec.linde.persistencia.entidades.ProdClientes;

/**
 * TODO: Agregar descripcion
 * 
 * @FechaCreacion: 29/09/2014
 * @author: Alex Carrillo
 * @FechaModificacion: 29/09/2014
 * @author: Alex Carrillo
 * @version: 1.0
 */
@Stateless
public class ClienteDao {

	@PersistenceContext(unitName = "productividadJPA")
	public static EntityManager em;

	/**
	 * Carga ProdContratista
	 * 
	 * @param idContratista
	 * @return ProdContratista
	 */
	public ProdClientes cargarDetalle(int idCliente) {
		ProdClientes cliente = buscarPorId(idCliente);
		cliente.getClieId();
		cliente.getListaContactoClientes().size();
		return cliente;
	}

	/**
	 * Guarda, actualiza, elimina nuevo objeto en la base de datos
	 * 
	 * @param entidad
	 */
	public void guardar(ProdClientes entidad) {
		if (entidad.getClieId() == 0) {
			insertar(entidad);
		} else {
			actualizar(entidad);
		}
	}

	/**
	 * 
	 * Inserta un objeto
	 * 
	 * @param entidad
	 */
	public void insertar(ProdClientes entidad) {
		em.persist(entidad);
	}

	/**
	 * Actualiza un objeto
	 * 
	 * @param entidad
	 */
	public void actualizar(ProdClientes entidad) {
		em.merge(entidad);
	}

	/**
	 * 
	 * Refresca la entidad
	 * 
	 * @param entidad
	 */
	public void refrescar(ProdClientes entidad) {
		em.refresh(entidad);
	}

	/**
	 * Elimina un objeto de la base de datos
	 * 
	 * @param entidad
	 */
	public void eliminar(ProdClientes entidad) {
		em.remove(em.merge(entidad));
	}

	/**
	 * Da la orden de escribir en la BDD
	 */
	public void flush() {
		em.flush();
	}

	/**
	 * Busca un objeto dado su llave primaria
	 * 
	 * @param id
	 * @return
	 */
	public ProdClientes buscarPorId(Object id) {
		return em.find(ProdClientes.class, id);
	}

	/**
	 * 
	 * Convierte a la entidad en una entidad no manejeda
	 * 
	 * @param entidad
	 */
	public void detach(ProdClientes entidad) {
		em.detach(entidad);
	}

	/**
	 * TODO: Agregar descripcion del metodo
	 * 
	 * @param startIndex
	 * @param pageSize
	 * @param typedQuery
	 */
	public void agregarPaginacion(int startIndex, int pageSize, TypedQuery<ProdClientes> typedQuery) {
		typedQuery.setFirstResult(startIndex);
		typedQuery.setMaxResults(pageSize);
	}

	/**
	 * 
	 * TODO: Agregar descripcion del metodo
	 * 
	 * @param startIndex
	 * @param pageSize
	 * @param sortField
	 * @param sortOrder
	 * @param filters
	 * @return
	 */
	public List<ProdClientes> obtenerListaPorPagina(int startIndex, int pageSize, String sortField, boolean sortOrder, Map<String, String> filters) {

		CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
		CriteriaQuery<ProdClientes> criteriaQuery = criteriaBuilder.createQuery(ProdClientes.class);
		Root<ProdClientes> from = criteriaQuery.from(ProdClientes.class);

		agregarOrdenamiento(sortField, sortOrder, criteriaBuilder, criteriaQuery, from);

		List<Expression<?>> expresiones = obtenerExpresiones(filters, criteriaBuilder, from);
		criteriaQuery.where(expresiones.toArray(new Predicate[expresiones.size()]));

		CriteriaQuery<ProdClientes> select = criteriaQuery.select(from);
		TypedQuery<ProdClientes> typedQuery = em.createQuery(select);

		agregarPaginacion(startIndex, pageSize, typedQuery);

		return typedQuery.getResultList();
	}

	/**
	 * 
	 * Retorna un lista para ser mostrado en un combo
	 * 
	 * @param sortField
	 * @param sortOrder
	 * @param filters
	 * @return
	 */
	public List<ProdClientes> obtenerListaCombo(String sortField, boolean sortOrder, Map<String, String> filters) {

		CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
		CriteriaQuery<ProdClientes> criteriaQuery = criteriaBuilder.createQuery(ProdClientes.class);
		Root<ProdClientes> from = criteriaQuery.from(ProdClientes.class);

		agregarOrdenamiento(sortField, sortOrder, criteriaBuilder, criteriaQuery, from);

		List<Expression<?>> empresiones = obtenerExpresiones(filters, criteriaBuilder, from);
		criteriaQuery.where(empresiones.toArray(new Predicate[empresiones.size()]));

		CriteriaQuery<ProdClientes> select = criteriaQuery.select(from);
		TypedQuery<ProdClientes> typedQuery = em.createQuery(select);

		return typedQuery.getResultList();
	}

	/**
	 * TODO: Agregar descripcion del metodo
	 * 
	 * @param sortField
	 * @param sortOrder
	 * @param criteriaBuilder
	 * @param criteriaQuery
	 * @param from
	 */
	public void agregarOrdenamiento(String sortField, boolean sortOrder, CriteriaBuilder criteriaBuilder, CriteriaQuery<ProdClientes> criteriaQuery,
			Root<ProdClientes> from) {

		if (sortField != null && !sortField.isEmpty()) {

			Path<String> path = null;
			String[] properties = sortField.split("\\.");

			if (properties.length == 1) {
				path = from.get(sortField);
			} else {

				sortField = properties[0];
				Join<Object, Object> joinTable = from.join(sortField);

				for (int i = 1; i < properties.length; i++) {
					sortField = properties[i];
					if (i < properties.length - 1) {
						joinTable = joinTable.join(sortField);
					} else {
						path = joinTable.get(sortField);
					}
				}
			}

			if (path != null) {
				if (sortOrder) {
					criteriaQuery.orderBy(criteriaBuilder.asc(path));
				} else {
					criteriaQuery.orderBy(criteriaBuilder.desc(path));
				}
			}
		}
	}

	/**
	 * Retorna el numero de registros dado un criterio
	 * 
	 * @param filters
	 * @return
	 */
	public int contarPorCriterio(Map<String, String> filters) {
		CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
		CriteriaQuery<Long> criteriaQuery = criteriaBuilder.createQuery(Long.class);

		Root<ProdClientes> from = criteriaQuery.from(ProdClientes.class);
		criteriaQuery.select(criteriaBuilder.count(from));

		List<Expression<?>> expresiones = obtenerExpresiones(filters, criteriaBuilder, from);
		criteriaQuery.where(expresiones.toArray(new Predicate[expresiones.size()]));

		return em.createQuery(criteriaQuery).getSingleResult().intValue();
	}

	/**
	 * 
	 * Forma los filtros de la entidad
	 * 
	 * @param filters
	 * @param criteriaBuilder
	 * @param from
	 * @return
	 */
	public List<Expression<?>> obtenerExpresiones(Map<String, String> filters, CriteriaBuilder criteriaBuilder, Root<ProdClientes> from) {

		if (filters == null) {
			filters = new HashMap<String, String>();
		}

		List<Expression<?>> expressions = new ArrayList<Expression<?>>();

		for (Iterator<String> it = filters.keySet().iterator(); it.hasNext();) {

			String filterProperty = it.next();
			String filterValue = filters.get(filterProperty);

			if (filterProperty != null && !filterProperty.isEmpty() && filterValue != null && !filterValue.isEmpty()) {

				String[] properties = filterProperty.split("\\.");

				if (properties.length == 1) {
					Expression<?> expression = from.get(filterProperty);
					agregarExpresion(criteriaBuilder, expressions, expression, filterValue, filterProperty);

				} else {

					filterProperty = properties[0];
					Join<Object, Object> joinTable = from.join(filterProperty);

					for (int i = 1; i < properties.length; i++) {
						filterProperty = properties[i];
						if (i < properties.length - 1) {
							joinTable = joinTable.join(filterProperty);
						} else {
							Expression<?> expression = joinTable.get(filterProperty);
							agregarExpresion(criteriaBuilder, expressions, expression, filterValue, filterProperty);
						}
					}
				}
			}

		}

		return expressions;
	}

	/**
	 * 
	 * TODO: Agregar descripcion del metodo
	 * 
	 * @param criteriaBuilder
	 * @param expressions
	 * @param filterValue
	 * @param expression
	 */
	private void agregarExpresion(CriteriaBuilder criteriaBuilder, List<Expression<?>> expressions, Expression<?> expression, String filterValue,
			String filterProperty) {

		String operador = "";
		if (filterValue.startsWith("!=")) {
			filterValue = filterValue.replace("!=", "");
			operador = "!=";
		} else if (filterValue.startsWith("=")) {
			filterValue = filterValue.replace("=", "");
			operador = "=";
		}

		if ("true".equals(filterValue) || "false".equals(filterValue)) {
			expressions.add(criteriaBuilder.equal(expression, Boolean.parseBoolean(filterValue)));

		} else {

			try {

				TipoNegocio tipoAtributo = TipoNegocio.valueOf(filterValue);
				expressions.add(criteriaBuilder.equal(expression, tipoAtributo));

			} catch (Exception ex4) {

				try {

					Estado estado = Estado.valueOf(filterValue);
					expressions.add(criteriaBuilder.equal(expression, estado));

				} catch (Exception ex5) {

					// idX = filtro por id
					if (filterProperty.length() >= 3 && Character.isUpperCase(filterProperty.toCharArray()[2])
							&& filterProperty.substring(0, 2).equals("id")) {
						try {
							expressions.add(criteriaBuilder.equal(expression, Integer.parseInt(filterValue)));
						} catch (NumberFormatException e) {
							expressions.add(criteriaBuilder.like(expression.as(String.class), filterValue + "%"));
						}
					} else {
						if ("=".equals(operador)) {
							expressions.add(criteriaBuilder.equal(expression, filterValue));
						} else {
							expressions.add(criteriaBuilder.like(expression.as(String.class), filterValue + "%"));
						}
					}
				}

			}

		}

	}

}
